/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// TGUI - Texus' Graphical User Interface
// Copyright (C) 2012-2025 Bruno Van de Velde (vdv_b@tgui.eu)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented;
//    you must not claim that you wrote the original software.
//    If you use this software in a product, an acknowledgment
//    in the product documentation would be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such,
//    and must not be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef TGUI_COMBO_BOX_HPP
#define TGUI_COMBO_BOX_HPP

#include <TGUI/Widgets/ListBox.hpp>
#include <TGUI/Renderers/ComboBoxRenderer.hpp>

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace tgui
{
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /// @brief Combo box widget
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    class TGUI_API ComboBox : public Widget
    {
    public:

        /// @brief The side where the list will be displayed
        enum class ExpandDirection
        {
            Down, //!< Display the list below the combo box
            Up,   //!< Display the list above the combo box
            Automatic  //!< Display the list below the combo box unless it wouldn't fit on the screen
        };

    public:

        using Ptr = std::shared_ptr<ComboBox>; //!< Shared widget pointer
        using ConstPtr = std::shared_ptr<const ComboBox>; //!< Shared constant widget pointer

        static constexpr const char StaticWidgetType[] = "ComboBox"; //!< Type name of the widget

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @internal
        /// @brief Constructor
        /// @param typeName     Type of the widget
        /// @param initRenderer Should the renderer be initialized? Should be true unless a derived class initializes it.
        /// @see create
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ComboBox(const char* typeName = StaticWidgetType, bool initRenderer = true);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Copy constructor
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ComboBox(const ComboBox& other);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Move constructor
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ComboBox(ComboBox&& other) noexcept;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Copy assignment operator
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ComboBox& operator= (const ComboBox& other);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Move assignment operator
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ComboBox& operator= (ComboBox&& other) noexcept;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Creates a new combo box widget
        ///
        /// @return The new combo box
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD static ComboBox::Ptr create();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Makes a copy of another combo box
        ///
        /// @param comboBox  The other combo box
        ///
        /// @return The new combo box
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD static ComboBox::Ptr copy(const ComboBox::ConstPtr& comboBox);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the renderer, which gives access to functions that determine how the widget is displayed
        /// @return Temporary pointer to the renderer that may be shared with other widgets using the same renderer
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD ComboBoxRenderer* getSharedRenderer() override;
        TGUI_NODISCARD const ComboBoxRenderer* getSharedRenderer() const override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the renderer, which gives access to functions that determine how the widget is displayed
        /// @return Temporary pointer to the renderer
        /// @warning After calling this function, the widget has its own copy of the renderer and it will no longer be shared.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD TGUI_NODISCARD ComboBoxRenderer* getRenderer() override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Changes the size of the combo box
        ///
        /// This size does not include the borders.
        ///
        /// @param size   The new size of the combo box
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void setSize(const Layout2d& size) override;
        using Widget::setSize;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Enables or disables the widget
        /// @param enabled  Is the widget enabled?
        ///
        /// The disabled widget will no longer receive events and thus no longer send callbacks.
        /// All widgets are enabled by default.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void setEnabled(bool enabled) override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Changes the number of items that are displayed in the list
        ///
        /// @param nrOfItemsInListToDisplay  The maximum number of items to display when the list of items is shown
        ///
        /// When there is no scrollbar then this is the maximum number of items.
        /// If there is one, then it will only become visible when there are more items than this number.
        ///
        /// When set to zero then all items are shown (then there will never be a scrollbar).
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void setItemsToDisplay(std::size_t nrOfItemsInListToDisplay);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the number of items that are displayed in the list
        ///
        /// @return The maximum number of items to display when the list of items is shown
        ///
        /// When there is no scrollbar then this is the maximum number of items.
        /// If there is one, then it will only become visible when there are more items than this number.
        ///
        /// When set to zero then all items are shown (then there will never be a scrollbar).
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD std::size_t getItemsToDisplay() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Adds an item to the list, so that it can be selected later
        ///
        /// @param itemName  The name of the item you want to add (this is the text that will be displayed inside the combo box)
        /// @param id        Optional unique id given to this item for the purpose to later identifying this item
        ///
        /// @return
        ///   - Index of inserted item if no maximum items are set or when index is still less than getMaximumItems()
        ///   - Value of getMaximumItems() when adding item failed because there are too many items
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        std::size_t addItem(const String& itemName, const String& id = "");

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Adds multiple items to the list
        ///
        /// @param itemNames  List of items that should be passed to the addItem function
        ///
        /// If a maximum items was configured then this function stops adding items when the maximum is reached.
        ///
        /// @since TGUI 1.6
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void addMultipleItems(const std::vector<String>& itemNames);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Selects an item from the list
        ///
        /// When adding items to the combo box with the addItem function, none of them will be selected.
        /// If you don't want the combo box to stay empty until the user selects something, but you want a default item instead,
        /// then you can use this function to select an item.
        ///
        /// @param itemName  The item you want to select
        ///
        /// In case the names are not unique, the first item with that name will be selected.
        ///
        /// @return
        ///         - true on success
        ///         - false when none of the items matches the name
        ///
        /// @see setSelectedItemById
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        bool setSelectedItem(const String& itemName);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Selects an item from the list
        ///
        /// When adding items to the combo box with the addItem function, none of them will be selected.
        /// If you don't want the combo box to stay empty until the user selects something, but you want a default item instead,
        /// then you can use this function to select an item.
        ///
        /// @param id  Unique id passed to addItem
        ///
        /// In case the id would not be unique, the first item with that id will be selected.
        ///
        /// @return
        ///         - true on success
        ///         - false when none of the items has the given id
        ///
        /// @see setSelectedItem
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        bool setSelectedItemById(const String& id);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Selects an item in the list
        ///
        /// @param index  Index of the item in the list
        ///
        /// @return
        ///         - true on success
        ///         - false when the index was too high
        ///
        /// @see setSelectedItem
        /// @see setSelectedItemById
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        bool setSelectedItemByIndex(std::size_t index);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Deselects the selected item
        ///
        /// The combo box will be empty after this function is called.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void deselectItem();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Removes the item from the list with the given name
        ///
        /// @param itemName  The item to remove
        ///
        /// In case the names are not unique, only the first item with that name will be removed.
        ///
        /// @return
        ///        - true when the item was removed
        ///        - false when the name did not match any item
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        bool removeItem(const String& itemName);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Removes the item that were added with the given id
        ///
        /// @param id  Id that was given to the addItem function
        ///
        /// In case the id is not unique, only the first item with that id will be removed.
        ///
        /// @return
        ///        - true when the item was removed
        ///        - false when there was no item with the given id
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        bool removeItemById(const String& id);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Removes the item from the list
        ///
        /// @param index  Index of the item in the list
        ///
        /// @return
        ///        - true when the item was removed
        ///        - false when the index was too high
        ///
        /// @see removeItem
        /// @see removeItemById
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        bool removeItemByIndex(std::size_t index);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Removes all items from the list
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void removeAllItems();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the item name of the item with the given id
        ///
        /// @param id  The id of the item that was given to it when it was added
        ///
        /// In case the id is not unique, the first item with that id will be returned.
        ///
        /// @return The requested item, or an empty string when no item matches the id
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD String getItemById(const String& id) const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the item name of the item at the given index
        ///
        /// @param index  The index of the item to return
        ///
        /// @return The requested item, or an empty string when the index was too high
        ///
        /// @since TGUI 1.6
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD String getItemByIndex(std::size_t index) const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the index of the item with the given id
        ///
        /// @param id  The id of the item that was given to it when it was added
        ///
        /// In case the id is not unique, the index of the first item with that id will be returned.
        ///
        /// @return The index of the item that matched the id, or -1 when no item matches the id
        ///
        /// @since TGUI 1.6
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD int getIndexById(const String& id) const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the id of the item at the given index
        ///
        /// @param index  The index of the item to find
        ///
        /// @return The id of the requested item, or an empty string when the index was too high
        ///
        /// @since TGUI 1.6
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD String getIdByIndex(std::size_t index) const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the currently selected item
        ///
        /// @return The selected item.
        ///         When no item was selected then this function will return an empty string
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD String getSelectedItem() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Gets the id of the selected item
        ///
        /// @return The id of the selected item, which was the optional id passed to the addItem function.
        ///         When no item was selected then this function returns 0
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD String getSelectedItemId() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Gets the index of the selected item
        ///
        /// @return The index of the selected item, or -1 when no item was selected
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD int getSelectedItemIndex() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Changes an item with name originalValue to newValue
        ///
        /// @param originalValue The name of the item which you want to change
        /// @param newValue      The new name for that item
        ///
        /// In case the names are not unique, only the first item with that name will be changed.
        ///
        /// @return
        ///        - true when the item was changed
        ///        - false when none of the items had the given name
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        bool changeItem(const String& originalValue, const String& newValue);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Changes the name of an item with the given id to newValue
        ///
        /// @param id       The unique id of the item which you want to change
        /// @param newValue The new name for that item
        ///
        /// In case the id is not unique, only the first item with that id will be changed.
        ///
        /// @return
        ///        - true when the item was changed
        ///        - false when none of the items had the given id
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        bool changeItemById(const String& id, const String& newValue);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Changes the name of an item at the given index to newValue
        ///
        /// @param index    The index of the item which you want to change
        /// @param newValue The new name for that item
        ///
        /// @return
        ///        - true when the item was changed
        ///        - false when the index was too high
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        bool changeItemByIndex(std::size_t index, const String& newValue);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the amount of items in the combo box
        ///
        /// @return Number of items inside the combo box
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD std::size_t getItemCount() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns a copy of the items in the combo box
        ///
        /// @return items
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD std::vector<String> getItems() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns a copy of the item ids in the combo box
        ///
        /// @return item ids
        ///
        /// Items that were not given an id simply have an empty string as id.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD std::vector<String> getItemIds() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Store some user data with the item
        ///
        /// @param index Index of the item
        /// @param data  User data to store
        ///
        /// Examples:
        /// @code
        /// comboBox->setItemData(idx, "Data"); // Note: type to retrieve with getItemData is 'const char*' here
        /// comboBox->setItemData(idx, 5);
        /// @endcode
        ///
        /// @since TGUI 1.3
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void setItemData(std::size_t index, Any data);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns user data stored in the item
        /// @return Stored data
        /// @throw std::bad_cast if the template type does not match the type inside the std::any variable passed in setItemData
        ///        or when the index was too high (which acts as if you access an empty std::any variable).
        ///
        /// @since TGUI 1.3
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        template <typename DataType>
        TGUI_NODISCARD DataType getItemData(std::size_t index) const
        {
            return m_listBox->getItemData<DataType>(index);
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Changes the maximum items that the combo box can contain
        ///
        /// @param maximumItems  The maximum items inside the combo box.
        ///                      When the maximum is set to 0 then the limit will be disabled
        ///
        /// If no scrollbar was loaded then there is always a limitation because there will be a limited space for the items.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void setMaximumItems(std::size_t maximumItems = 0);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the maximum items that the combo box can contain
        ///
        /// @return The maximum items inside the list.
        ///         If the function returns 0 then there is no limit
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD std::size_t getMaximumItems() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Changes the default text of the combo box. This is the text drawn when no item is selected.
        ///
        /// @param defaultText  The new default text
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void setDefaultText(const String& defaultText);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the default text of the combo box. This is the text drawn when no item is selected.
        ///
        /// @return The default text of the combo box
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD const String& getDefaultText() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Changes the side where the list is displayed
        /// @param direction  On which side of the combo box should the list be displayed?
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void setExpandDirection(ExpandDirection direction);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns the side where the list is displayed
        /// @return On which side of the combo box is the list be displayed?
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD ExpandDirection getExpandDirection() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns whether the combo box contains the given item
        /// @return Does the combo box contain the item?
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD bool contains(const String& item) const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns whether the combo box contains an item with the given id
        /// @return Does the combo box contain the id?
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD bool containsId(const String& id) const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Changes whether the mouse wheel can be used to change the selected item while the list is closed
        ///
        /// @param changeOnScroll  Should the selected item be changed when scrolling while the mouse is on top of the combo box?
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void setChangeItemOnScroll(bool changeOnScroll);

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns whether the mouse wheel can be used to change the selected item while the list is closed
        ///
        /// @return Should the selected item be changed when scrolling while the mouse is on top of the combo box?
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD bool getChangeItemOnScroll() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @internal
        /// This function is called when the widget is added to a container.
        /// You should not call this function yourself.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void setParent(Container* parent) override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Returns whether the mouse position (which is relative to the parent widget) lies on top of the widget
        ///
        /// @return Is the mouse on top of the widget?
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD bool isMouseOnWidget(Vector2f pos) const override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @internal
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        bool leftMousePressed(Vector2f pos) override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @internal
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        bool scrolled(float delta, Vector2f pos, bool touch) override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Draw the widget to a render target
        ///
        /// @param target Render target to draw to
        /// @param states Current render states
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void draw(BackendRenderTarget& target, RenderStates states) const override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    protected:

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Retrieves a signal based on its name
        ///
        /// @param signalName  Name of the signal
        ///
        /// @return Signal that corresponds to the name
        ///
        /// @throw Exception when the name does not match any signal
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD Signal& getSignal(String signalName) override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Function called when one of the properties of the renderer is changed
        ///
        /// @param property  Name of the property that was changed
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void rendererChanged(const String& property) override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Saves the widget as a tree node in order to save it to a file
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD std::unique_ptr<DataIO::Node> save(SavingRenderersMap& renderers) const override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Loads the widget from a tree of nodes
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void load(const std::unique_ptr<DataIO::Node>& node, const LoadingRenderersMap& renderers) override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// @brief Called when the text size is changed (either by setTextSize or via the renderer)
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void updateTextSize() override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Returns the size without the borders
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD Vector2f getInnerSize() const;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Update the height of the internal list box
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void updateListBoxHeight();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Shows the list of items.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void showListBox();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Hides the list of items.
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void hideListBox();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Initialize the internal list box
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void initListBox();

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Makes a copy of the widget
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        TGUI_NODISCARD Widget::Ptr clone() const override;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    public:

        SignalItem onItemSelect = {"ItemSelected"}; //!< An item was selected in the combo box. Optional parameter: selected item or its index

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    protected:

        // The number of items to display. If there is a scrollbar then you can scroll to see the other.
        // If there is no scrollbar then this will be the maximum amount of items.
        std::size_t m_nrOfItemsToDisplay = 0;

        // Internally a list box is used to store all items
        ListBox::Ptr m_listBox = ListBox::create();

        Text m_text;
        Text m_defaultText;

        int m_previousSelectedItemIndex = -1;
        bool m_changeItemOnScroll = false;

        ExpandDirection m_expandDirection = ExpandDirection::Automatic;

        Sprite m_spriteBackground;
        Sprite m_spriteBackgroundDisabled;
        Sprite m_spriteArrow;
        Sprite m_spriteArrowHover;
        Sprite m_spriteArrowDisabled;

        // Cached renderer properties
        Borders m_bordersCached;
        Padding m_paddingCached;
        Color   m_borderColorCached;
        Color   m_backgroundColorCached;
        Color   m_backgroundColorDisabledCached;
        Color   m_arrowColorCached;
        Color   m_arrowColorHoverCached;
        Color   m_arrowColorDisabledCached;
        Color   m_arrowBackgroundColorCached;
        Color   m_arrowBackgroundColorHoverCached;
        Color   m_arrowBackgroundColorDisabledCached;
        Color   m_textColorCached;
        Color   m_textColorDisabledCached;
        float   m_roundedBorderRadiusCached = 0;

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    };

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#endif // TGUI_COMBO_BOX_HPP

